﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace MartinRL.ProjectEuler.Sequences
{
    public class LongSequence : IEnumerable<long>
    {
        private readonly NextTermMode nextTermMode;
        private long startTerm;

        private LongSequence(NextTermMode nextTermMode = NextTermMode.Increment)
        {
            this.nextTermMode = nextTermMode;
        }

        public static LongSequence NewSequence()
        {
            return new LongSequence();
        }

        public static LongSequence NewDecrementingSequence()
        {
            return new LongSequence(NextTermMode.Decrement);
        }

        public IEnumerator<long> GetEnumerator()
        {
            return new TermEnumerator(startTerm, nextTermMode);
        }

        public class TermEnumerator : IEnumerator<long>
        {
            // could use «strategy», «template method» or the like, but feels like overkill as of now.
            private readonly NextTermMode nextTermMode; 

            public TermEnumerator(long startTerm, NextTermMode nextTermMode)
            {
                this.nextTermMode = nextTermMode;

                InitializeCurrent(startTerm);
            }

            private void InitializeCurrent(long startTerm)
            {
                Current = startTerm;

                switch (nextTermMode)
                {
                    case NextTermMode.Increment:
                        Decrement();
                        break;

                    case NextTermMode.Decrement:
                        Increment();
                        break;
                }
            }

            public void Dispose() { }

            public bool MoveNext()
            {
                switch (nextTermMode)
                {
                    case NextTermMode.Increment:
                        Increment();
                        break;

                    case NextTermMode.Decrement:
                        Decrement();
                        break;
                }

                return true;
            }

            private void Decrement()
            {
                Current--;
            }

            private void Increment()
            {
                Current++;
            }

            public void Reset()
            {
                throw new NotImplementedException();
            }

            public long Current { get; private set; }

            object IEnumerator.Current
            {
                get { return Current; }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public LongSequence Start(long startTerm)
        {
            this.startTerm = startTerm;

            return this;
        }

        public IEnumerable<long> End(long endTerm)
        {
            // bug: doesn't work for decrementing sequence
            return this.TakeWhile(t => t <= endTerm);
        }
    }
}